








#include <libfdt.h>
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "hw/boards.h"
#include "hw/qdev-properties.h"
#include "sysemu/sysemu.h"
#include "sysemu/device_tree.h"
#include "chardev/char.h"
#include "qemu/error-report.h"
#include "qemu/module.h"
#include "qemu/units.h"

#include "hw/char/serial.h"
#include "hw/ssi/ssi.h"
#include "net/net.h"
#include "hw/sysbus.h"
#include "target/arm/cpu.h"
#include "hw/char/pl011.h"
#include "hw/misc/unimp.h"
#include "hw/arm/hi1711.h"

#define NAME_SIZE 32
#define REG_SHIFT 2
#define BAUD_BASE 115200

#define ARCH_TIMER_VIRT_IRQ   11
#define ARCH_TIMER_S_EL1_IRQ  13
#define ARCH_TIMER_NS_EL1_IRQ 14
#define ARCH_TIMER_NS_EL2_IRQ 10

static void hi1711_init(Object *obj)
{
    MachineState *ms = MACHINE(qdev_get_machine());
    HI1711_MachineState *s = HI1711(obj);
    char name[NAME_SIZE];
    int i;

    for (i = 0; i < MIN(ms->smp.cpus, HI1711_NUM_CPUS); i++) {




        snprintf(name, NAME_SIZE, "cpu%d", i);
        object_initialize_child(obj, name, &s->cpu[i], ARM_CPU_TYPE_NAME("cortex-a55"));
    }
}

#define CREATE_UART(grp, idx) pl011_create(ms->memmap[grp].base, qdev_get_gpio_in(dev, ms->irqmap[grp]), \
        serial_hd(idx))
static void create_uart(DeviceState *dev, HI1711_MachineState *ms)
{
    /* UART: 8250 */
    for (uint32_t i = 0; i < HI1711_NUM_UART; i++) {
        serial_mm_init(get_system_memory(), ms->memmap[HI1711_UART0 + i].base, REG_SHIFT,
                        qdev_get_gpio_in(dev, ms->irqmap[HI1711_UART0 + i]), BAUD_BASE,
                        serial_hd(i), DEVICE_NATIVE_ENDIAN);
    }
}

static void create_gpio(DeviceState *dev, HI1711_MachineState *ms)
{
    const uint32_t index[] = {
        HI1711_GPIO0, HI1711_GPIO1, HI1711_GPIO2, HI1711_GPIO3, HI1711_GPIO4 
    };

    for (uint32_t i = 0; i < HI1711_NUM_GPIO; i++) {
        DeviceState *s = qdev_new("hs0606");
        sysbus_realize_and_unref(SYS_BUS_DEVICE(s), &error_fatal);
        sysbus_mmio_map(SYS_BUS_DEVICE(s), 0, ms->memmap[index[i]].base);
        sysbus_connect_irq(SYS_BUS_DEVICE(s), 0, qdev_get_gpio_in(ms->gic, ms->irqmap[index[i]]));
    }
}

#define ETH2_INDEX 2
#define ETH3_INDEX 3
static void create_gmac(DeviceState *dev, HI1711_MachineState *ms)
{
    DeviceState *gmac;
    for (uint32_t i = 0; i < HI1711_NUM_GMAC; i++) {
        gmac = qdev_try_new("hs1304.gmac");
        if (!gmac) {
            return;
        }
        if (i == 0) {
            NetClientState *nc = qemu_find_netdev("eth0");
            if (nc != NULL) {
                qdev_prop_set_netdev(gmac, "netdev", nc);
            }
        } else if (i == 1) {
            NetClientState *nc = qemu_find_netdev("eth1");
            if (nc != NULL) {
                qdev_prop_set_netdev(gmac, "netdev", nc);
            }
        } else if (i == ETH2_INDEX) {
            NetClientState *nc = qemu_find_netdev("eth2");
            if (nc != NULL) {
                qdev_prop_set_netdev(gmac, "netdev", nc);
            }
        } else if (i == ETH3_INDEX) {
            NetClientState *nc = qemu_find_netdev("eth3");
            if (nc != NULL) {
                qdev_prop_set_netdev(gmac, "netdev", nc);
            }
        }

        qdev_prop_set_uint32(gmac, "index", i);
        sysbus_realize_and_unref(SYS_BUS_DEVICE(gmac), &error_abort);
        sysbus_mmio_map(SYS_BUS_DEVICE(gmac), 0, ms->memmap[HI1711_GMAC0 + i].base);
        sysbus_connect_irq(SYS_BUS_DEVICE(gmac), 0, qdev_get_gpio_in(ms->gic, ms->irqmap[HI1711_GMAC0 + i]));
    }
}

static void create_virtio(DeviceState *dev, HI1711_MachineState *ms)
{
    qemu_irq irq0 = qdev_get_gpio_in(dev, ms->irqmap[HI1711_VIRTIO0]);
    hwaddr base0 = ms->memmap[HI1711_VIRTIO0].base;
    sysbus_create_simple("virtio-mmio", base0, irq0);

    qemu_irq irq1 = qdev_get_gpio_in(dev, ms->irqmap[HI1711_VIRTIO1]);
    hwaddr base1 = ms->memmap[HI1711_VIRTIO1].base;
    sysbus_create_simple("virtio-mmio", base1, irq1);

    qemu_irq irq2 = qdev_get_gpio_in(dev, ms->irqmap[HI1711_VIRTIO2]);
    hwaddr base2 = ms->memmap[HI1711_VIRTIO2].base;
    sysbus_create_simple("virtio-mmio", base2, irq2);
}

static void create_i2c(DeviceState *dev, HI1711_MachineState *ms)
{
    const uint32_t index[] = {
        HI1711_I2C0, HI1711_I2C1, HI1711_I2C2, HI1711_I2C3,
        HI1711_I2C4, HI1711_I2C5, HI1711_I2C6, HI1711_I2C7,
        HI1711_I2C8, HI1711_I2C9, HI1711_I2C10, HI1711_I2C11,
        HI1711_I2C12, HI1711_I2C13, HI1711_I2C14, HI1711_I2C15
    };

    for (uint32_t i = 0; i < HI1711_NUM_I2C; i++) {
        DeviceState *s = qdev_new("hs0703");
        if (!s) {
            return;
        }

        sysbus_realize_and_unref(SYS_BUS_DEVICE(s), &error_fatal);
        sysbus_mmio_map(SYS_BUS_DEVICE(s), 0, ms->memmap[index[i]].base);
        sysbus_connect_irq(SYS_BUS_DEVICE(s), 0, qdev_get_gpio_in(ms->gic, ms->irqmap[index[i]]));

        ms->i2c[i] = s;
    }
}

static void create_ipmb(DeviceState *dev, HI1711_MachineState *ms)
{
    const uint32_t index[] = {
		HI1711_IPMB0,
		HI1711_IPMB1,
		HI1711_IPMB2,
		HI1711_IPMB3
    };

    for (uint32_t i = 0; i < 1; i++) {
        DeviceState *s = qdev_new("hs0704");
        if (!s) {
            return;
        }

        sysbus_realize_and_unref(SYS_BUS_DEVICE(s), &error_fatal);
        sysbus_mmio_map(SYS_BUS_DEVICE(s), 0, ms->memmap[index[i]].base);
        sysbus_connect_irq(SYS_BUS_DEVICE(s), 0, qdev_get_gpio_in(ms->gic, ms->irqmap[index[i]]));

        ms->ipmb[i] = s;
    }
}

static void create_trng(DeviceState *dev, HI1711_MachineState *ms)
{
    DeviceState *s = qdev_new("hs2304");
    if (!s) {
        return;
    }

    sysbus_realize_and_unref(SYS_BUS_DEVICE(s), &error_fatal);
    sysbus_mmio_map(SYS_BUS_DEVICE(s), 0, ms->memmap[HI1711_TRNG].base);
    sysbus_connect_irq(SYS_BUS_DEVICE(s), 0, qdev_get_gpio_in(ms->gic, ms->irqmap[HI1711_TRNG]));

    ms->trng = s;
}

static void create_lp_subsys(DeviceState *dev, HI1711_MachineState *ms)
{
    if (ms->memmap[HI1711_LP_SUBSYS].size) {
        DeviceState *lp_sub = qdev_new("lp_subsys");
        object_property_add_child(OBJECT(dev), "lp_subsys", OBJECT(lp_sub));
        sysbus_realize_and_unref(SYS_BUS_DEVICE(lp_sub), &error_abort);
        sysbus_mmio_map(SYS_BUS_DEVICE(lp_sub), 0, ms->memmap[HI1711_LP_SUBSYS].base);
        ms->lp_subsys=lp_sub;
    }
}

static void create_wdg(DeviceState *dev, HI1711_MachineState *ms)
{
    const uint32_t index[] = {
        HI1711_WDG0, HI1711_WDG1, HI1711_WDG2, HI1711_WDG3, HI1711_WDG4, HI1711_WDG5,
    };

    for (uint32_t i = 0; i < HI1711_NUM_WDG; i++) {
        DeviceState *s = qdev_new("hs1803");
        if (!s) {
            return;
        }

        sysbus_realize_and_unref(SYS_BUS_DEVICE(s), &error_fatal);
        sysbus_mmio_map(SYS_BUS_DEVICE(s), 0, ms->memmap[index[i]].base);

        ms->wdg[i] = s;
    }
}

static void create_efuse(DeviceState *dev, HI1711_MachineState *ms)
{
    const uint32_t index[] = {
        HI1711_EFUSE0, HI1711_EFUSE1,
    };

    for (uint32_t i = 0; i < HI1711_NUM_EFUSE; i++) {
        DeviceState *s = qdev_new("efuse");
        if (!s) {
            return;
        }

        sysbus_realize_and_unref(SYS_BUS_DEVICE(s), &error_fatal);
        sysbus_mmio_map(SYS_BUS_DEVICE(s), 0, ms->memmap[index[i]].base);

        ms->efuse[i] = s;
    }
}

static void create_localbus(DeviceState *dev, HI1711_MachineState *ms)
{
    DeviceState *s = qdev_new("hs2101");
    if (!s) {
        return;
    }

    sysbus_realize_and_unref(SYS_BUS_DEVICE(s), &error_fatal);
    sysbus_mmio_map(SYS_BUS_DEVICE(s), 0, ms->memmap[HI1711_LOCALBUS].base);
    sysbus_connect_irq(SYS_BUS_DEVICE(s), 0, qdev_get_gpio_in(ms->gic, ms->irqmap[HI1711_LOCALBUS]));

    ms->localbus = s;
}

static void hi1711_realize(DeviceState *dev, Error **errp)
{
    MachineState *machine = MACHINE(qdev_get_machine());
    HI1711_MachineState *s = HI1711(dev);
    uint16_t i;
    unsigned int smp_cpus = machine->smp.cpus;

    if (smp_cpus > HI1711_NUM_CPUS) {
        error_setg(errp, "%s: Only %d CPUs are supported (%d requested)",
                    TYPE_HI1711, HI1711_NUM_CPUS, smp_cpus);
        return;
    }

    for (i = 0; i < smp_cpus; i++) {
        Object *cpuobj = OBJECT(&s->cpu[i]);
        CPU(cpuobj)->cpu_index = i;

        object_property_set_bool(cpuobj, "has_el3", true, NULL);
        object_property_set_bool(cpuobj, "has_el2", true, NULL);

        object_property_set_uint(cpuobj, "mp-affinity", i << 8, NULL);
        object_property_set_int(cpuobj, "psci-conduit", QEMU_PSCI_CONDUIT_SMC, NULL);
        object_property_set_link(cpuobj, "memory", OBJECT(get_system_memory()), &error_abort);
        object_property_set_int(cpuobj, "reset-cbar", s->memmap[HI1711_GIC_GICD].base, &error_abort);
        if (i) {
            object_property_set_bool(cpuobj, "start-powered-off", true, NULL);
        }

        qdev_realize(DEVICE(cpuobj), NULL, &error_abort);
    }

    // create gic
    s->gic = qdev_new("arm-gicv3");
    qdev_prop_set_uint32(s->gic, "revision", 3);
    qdev_prop_set_uint32(s->gic, "num-cpu", smp_cpus);
    qdev_prop_set_uint32(s->gic, "len-redist-region-count", 1);
    qdev_prop_set_uint32(s->gic, "redist-region-count[0]", REDIST_REGION_CNT);
    qdev_prop_set_uint32(s->gic, "num-irq", NUM_IRQS + GIC_INTERNAL);





    uint32_t redist0_capacity = s->memmap[HI1711_GIC_GICR].size / 0x20000;
    uint32_t redist0_count = MIN(smp_cpus, redist0_capacity);
    qdev_prop_set_uint32(s->gic, "redist-region-count[0]", redist0_count);

    // map gic MMIO
    SysBusDevice *gicbusdev = SYS_BUS_DEVICE(s->gic);
    sysbus_realize_and_unref(gicbusdev, &error_abort);
    sysbus_mmio_map(gicbusdev, 0, s->memmap[HI1711_GIC_GICD].base);
    sysbus_mmio_map(gicbusdev, 1, s->memmap[HI1711_GIC_GICR].base);

    // associate gic IRQs
    for (uint32_t i = 0; i < smp_cpus; i++) {
        DeviceState *cpudev = DEVICE(&s->cpu[i]);
        int ppibase = NUM_IRQS + i * GIC_INTERNAL + GIC_NR_SGIS;



        const int timer_irq[] = {
            [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ,
            [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ,
            [GTIMER_HYP] = ARCH_TIMER_NS_EL2_IRQ,
            [GTIMER_SEC] = ARCH_TIMER_S_EL1_IRQ,
        };

        for (int irq = 0; irq < ARRAY_SIZE(timer_irq); irq++) {
            qdev_connect_gpio_out(cpudev, irq, qdev_get_gpio_in(s->gic, ppibase + timer_irq[irq]));
        }
        qdev_connect_gpio_out_named(cpudev, "pmu-interrupt", 0, qdev_get_gpio_in(s->gic, i == 0 ? 32 : 115));

        sysbus_connect_irq(gicbusdev, i, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ));
        sysbus_connect_irq(gicbusdev, i + smp_cpus, qdev_get_gpio_in(cpudev, ARM_CPU_FIQ));
        sysbus_connect_irq(gicbusdev, i + ARM_CPU_VIRQ * smp_cpus, qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ));
        sysbus_connect_irq(gicbusdev, i + ARM_CPU_VFIQ * smp_cpus, qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ));
    }

    create_uart(DEVICE(s->gic), s);
    
    create_gpio(DEVICE(s->gic), s);

    create_gmac(DEVICE(s->gic), s);

    create_virtio(DEVICE(s->gic), s);

    create_i2c(DEVICE(s->gic), s);

	create_ipmb(DEVICE(s->gic), s);

    create_trng(DEVICE(s->gic), s);

    create_lp_subsys(DEVICE(s->gic), s);

    create_wdg(DEVICE(s->gic), s);

    create_efuse(DEVICE(s->gic), s);

    create_localbus(DEVICE(s->gic), s);
}

static void hi1711_class_init(ObjectClass *oc, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(oc);

    dc->realize = hi1711_realize;
    dc->desc = "HI1711 SOC";

    dc->user_creatable = false;
}

static const TypeInfo HI1711_type_info = {
    .name = TYPE_HI1711,
    .parent = TYPE_DEVICE,
    .instance_size = sizeof(HI1711_MachineState),
    .instance_init = hi1711_init,
    .class_init = hi1711_class_init,
};

static void hi1711_register_types(void)
{
    type_register_static(&HI1711_type_info);
}

type_init(hi1711_register_types)
